/**
 * 
 */
package libraries;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ExecutionException;

import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;

import jaxb.util.UUIDSetToArrayAdapter;
import metagrammar.Operator;
import metagrammar.Type;
import parsetree.OperatorSpec;
import util.Named;
import application.Application;
import application.GlobalContext;

import com.google.common.collect.ImmutableMap;

/**
 * A library is a set of {@link Type Types} and {@link Operator Operators}
 * which create a useful logical grammar.
 * 
 * @author Joshua Lockerman
 *
 */
@XmlRootElement
public class Library implements Named {
    
    public static Library Known = new KnownLibrary();
    public static Library Everything = new EverythingLibrary();
    
    private static class KnownLibrary extends Library {
        public KnownLibrary()
        {
            super("Known", Collections.<Type> emptyList(), Collections.<Operator> emptyList());
        }
        
        @Override
        public Collection<Type> loadTypes() throws ExecutionException
        {
            return Application.getContext().getKnownTypes();
        }
        
        @Override
        public Collection<Operator> loadOperators() throws ExecutionException
        {
            return Application.getContext().getKnownOperators();
        }
        
        @Override
        public Collection<OperatorSpec> loadSpecs() throws ExecutionException
        {
            Collection<Operator> operators = loadOperators();
            GlobalContext context = Application.getContext();
            ArrayList<OperatorSpec> specs = new ArrayList<OperatorSpec>(operators.size());
            for(Operator op : operators) {
                specs.addAll(context.getSpecs(op));
            }
            return specs;
        }
    }
    
    private static class EverythingLibrary extends Library {
        public EverythingLibrary()
        {
            super("Everything", Collections.<Type> emptyList(), Collections.<Operator> emptyList());
        }
        
        @Override
        public Collection<Type> loadTypes() throws ExecutionException
        {
            return Application.getContext().tempGetAllTypes();
        }
        
        @Override
        public Collection<Operator> loadOperators() throws ExecutionException
        {
            return Application.getContext().tempGetAllOperators();
        }
        
        @Override
        public Collection<OperatorSpec> loadSpecs() throws ExecutionException
        {
            Collection<Operator> operators = loadOperators();
            GlobalContext context = Application.getContext();
            ArrayList<OperatorSpec> specs = new ArrayList<OperatorSpec>(operators.size());
            for(Operator op : operators) {
                specs.addAll(context.getSpecs(op));
            }
            return specs;
        }
    }
    
    @XmlAttribute
    private final String name;
    
    @XmlJavaTypeAdapter(UUIDSetToArrayAdapter.class)
    private final HashSet<UUID> typeIDs;
    @XmlJavaTypeAdapter(UUIDSetToArrayAdapter.class)
    private final HashSet<UUID> operatorIDs;
    
    private final ImmutableMap<OperatorSpec, Float> precedences;
    
    //For serialization
    private Library()
    {
        name = null;
        typeIDs = null;
        operatorIDs = null;
        precedences = ImmutableMap.<OperatorSpec, Float> builder().build();
    }
    
    public Library(String name, Iterable<Type> types, Iterable<Operator> operators)
    {
        this(name, types, operators, null);
    }
    
    public Library(String name, Iterable<Type> types, Iterable<Operator> operators, Map<OperatorSpec, Float> precedences) {
        this.name = name;
        this.typeIDs = new HashSet<UUID>();
        this.operatorIDs = new HashSet<UUID>();
        for(Type type : types) {
            this.typeIDs.add(type.getId());
        }
        for(Operator op : operators) {
            this.operatorIDs.add(op.getId());
        }
        if(precedences != null) {
            this.precedences = ImmutableMap.<OperatorSpec, Float> builder().putAll(precedences)
                    .build();
        }
        else {
            this.precedences = ImmutableMap.<OperatorSpec, Float> builder().build();
        }
        
    }
    
    public Collection<Type> loadTypes() throws ExecutionException
    {
        GlobalContext context = Application.getContext();
        ArrayList<Type> types = new ArrayList<Type>(typeIDs.size());
        for(UUID id : typeIDs) {
            types.add(context.getType(id));
        }
        return types;
    }
    
    public Collection<Operator> loadOperators() throws ExecutionException
    {
        GlobalContext context = Application.getContext();
        ArrayList<Operator> operators = new ArrayList<Operator>(operatorIDs.size());
        for(UUID id : operatorIDs) {
            operators.add(context.getOperator(id));
        }
        return operators;
    }
    
    /**
     * Loads the {@link Operator Operators} and {@link OperatorSpec OperatorSpecs} relevant 
     * to this library form {@link GlobalContext} and returns the {@link OperatorSpec OperatorSpecs}.
     * This method is the only one that needs to be called to initialize the library for use.
     * 
     * @return a collection containing the {@link OperatorSpec OperatorSpecs} for all the {@link Operator Operators}
     * in this library
     * @throws ExecutionException
     */
    public Collection<OperatorSpec> loadSpecs() throws ExecutionException
    {
        GlobalContext context = Application.getContext();
        ArrayList<OperatorSpec> specs = new ArrayList<OperatorSpec>(operatorIDs.size());
        for(UUID id : operatorIDs) {
            Operator op = context.getOperator(id);
            Collection<OperatorSpec> opSpecs = context.getSpecs(op);
            if(opSpecs != null) {
                for(OperatorSpec spec : opSpecs) {
                    Float precedence = precedences.get(spec);
                    specs.ensureCapacity(specs.size() + opSpecs.size());
                    if(precedence != null) {
                        specs.add(spec.withPrecedence(precedence));
                    }
                    else {
                        specs.add(spec);
                    }
                }
            }
        }
        return specs;
    }
    
    @Override
    public String getName()
    {
        return name;
    }
    
    @Override
    public String toString()
    {
        return name;
    }
    
    /* (non-Javadoc)
     * @see java.lang.Object#hashCode()
     */
    @Override
    public int hashCode()
    {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((name == null) ? 0 : name.hashCode());
        return result;
    }
    
    /* (non-Javadoc)
     * @see java.lang.Object#equals(java.lang.Object)
     */
    @Override
    public boolean equals(Object obj)
    {
        if(this == obj) {
            return true;
        }
        if(obj == null) {
            return false;
        }
        if( !(obj instanceof Library)) {
            return false;
        }
        Library other = (Library) obj;
        if(name == null) {
            if(other.name != null) {
                return false;
            }
        }
        else if( !name.equals(other.name)) {
            return false;
        }
        return true;
    }
}
